<?php
/**
 * @desc Demo
 * @author Tinywan(ShaoBo Wan)
 * @email 756684177@qq.com
 * @date 2023/10/21 12:11
 */

require 'vendor/autoload.php';

use \WebSocket\Client;

class Spark
{
    /**
     * @desc run
     * @author Tinywan(ShaoBo Wan)
     */
    public function run()
    {
        try {
            // TODO 星火认知大模型APPID
            $appId = "xxxxxx";
            // TODO 星火认知大模型APPKEY
            $apikey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";
            // TODO 星火认知大模型APPSECRET
            $apiSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";
            
            // $gptUrl = "wss://spark-api.xf-yun.com/v1.1/chat";
            $gptUrl = "wss://spark-api.xf-yun.com/v2.1/chat";
            $authUrl = $this->sign($gptUrl, $apikey, $apiSecret);
            $client = new Client($authUrl);

            $question = "你是星火大模型2.0吗？";
            // $question = '使用PHP语言实现一个冒泡排序代码';
            echo "[发送请求][数据问题]：  " . $question . PHP_EOL;
            
            // 发送数据到 WebSocket 服务器
            $data = $this->getBody($appId, $question);
            $client->send($data);

            // 从 WebSocket 服务器接收数据
            $answer = "";
            while (true) {
                $response = $client->receive();
                // echo "[从服务器接收到的数据]： " . $response . PHP_EOL;
                $resp = json_decode($response, true);
                $code = $resp["header"]["code"];
                if (0 == $code) {
                    $status = $resp["header"]["status"];
                    $content = $resp['payload']['choices']['text'][0]['content'];
                    $answer .= $content;
                    if ($status == 2) {
                        $totalTokens = $resp['payload']['usage']['text']['total_tokens'];
                        // echo "[请求成功][本次消耗token用量]：  " . $totalTokens . PHP_EOL;
                        break;
                    }
                } else {
                    echo "[服务返回报错] " . $response . PHP_EOL;
                    break;
                }
            }
            echo "[请求成功][响应结果]：  " .PHP_EOL. $answer . PHP_EOL;
        } catch (Exception $e) {
            echo "[请求异常]  " . $e->getMessage() . PHP_EOL;
        }
    }

    /**
     * @desc 构造参数体
     * @param string $appid
     * @param string $question
     * @return false|string
     * @author Tinywan(ShaoBo Wan)
     */
    public function getBody(string $appid, string $question)
    {
        $header = [
            "app_id" => $appid,
            "uid" => "10086",
        ];

        $parameter = [
            "chat" => [
                // "domain" => "general", // 星火大模型V1.5请求地址，对应的domain参数为general
                "domain" => "generalv2", // 星火大模型V2请求地址，对应的domain参数为generalv2
                "temperature" => 0.5,
                "max_tokens" => 1024,
            ],
        ];

        $payload = [
            "message" => [
                "text" => [
                    // 需要联系上下文时，要按照下面的方式上传历史对话
                    // array("role" => "user", "content" => "你是谁"),
                    // array("role" => "assistant", "content" => "....."),
                    // ...省略的历史对话
                    [
                        "role" => "user", "content" => $question
                    ],
                ],
            ],
        ];

        return json_encode([
            "header" => $header,
            "parameter" => $parameter,
            "payload" => $payload,
        ], JSON_UNESCAPED_UNICODE);

    }

    /**
     * @desc 获取签名
     * @param string $gptUrl
     * @param string $apikey
     * @param string $apiSecret
     * @return string
     * @author Tinywan(ShaoBo Wan)
     */
    public function sign(string $gptUrl, string $apikey, string $apiSecret): string
    {
        $urlComponents = parse_url($gptUrl);
        $timestamp = time();
        // 生成RFC1123格式的时间戳
        $rfc1123FormatDate = DateTime::createFromFormat('U', $timestamp)->format('D, d M Y H:i:s \G\M\T');

        // authorization参数生成规则，拼接字符串
        $tmp = "host: " . $urlComponents['host'] . "\n";
        $tmp .= "date: " . $rfc1123FormatDate . "\n";
        $tmp .= "GET " . $urlComponents['path'] . " HTTP/1.1";
        // echo "[请求数据Header]  " . $tmp . PHP_EOL;

        // 利用hmac-sha256算法结合APISecret对上一步的tmp签名，获得签名后的摘要tmp_sha
        $signatureSha = hash_hmac('sha256', $tmp, $apiSecret, true);
        $signatureShaBase64 = base64_encode($signatureSha);
        $authorizationOrigin = 'api_key="' . $apikey . '", algorithm="hmac-sha256", headers="host date request-line", signature="' . $signatureShaBase64 . '"';
        $authorization = base64_encode($authorizationOrigin);

        // 将请求的鉴权参数组合为字典
        $map = [
            "authorization" => $authorization,
            "date" => $rfc1123FormatDate,
            "host" => $urlComponents['host']
        ];
        // 拼接鉴权参数，生成url
        return $gptUrl . '?' . http_build_query($map);
    }
}

$spark = new Spark();
$spark->run();